﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Forms.Integration;
using Ignitron.EEngine.Maths;
using Ignitron.EEngine.Lighting;
using Ignitron.EEngine.Model;
using SharpDX;
using SharpDX.Windows;
using SharpDX.Direct3D9;

using GraphicsClass = Ignitron.EEngine.Adapters.DX.Graphics.Graphics;
using Ignitron.EEngine.Adapters.DX.System;
using Ignitron.EEngine.Adapters.DX.Graphics;
using Ignitron.EEngine.Adapters.DX.Helper;


namespace Ignitron.EEngine.Adapters
{
    /// <summary>
    /// SharpDX Adapter class
    /// </summary>
    internal class SharpDXAdapter : IEngineExtended
    {
        /// <summary>
        /// The rendering control
        /// </summary>
        private RenderControl mRenderControl;

        /// <summary>
        /// The host for the control
        /// </summary>
        private WindowsFormsHost mHost;

        /// <summary>
        /// The rendering class
        /// </summary>
        private GraphicsClass mGraphics;

        /// <summary>
        /// The SharpDX configuration
        /// </summary>
        private SystemConfiguration mConfiguration;

        /// <summary>
        /// The selected SharpDX texture
        /// </summary>
        private SharpDXTexture mCurrentTexture;

        /// <summary>
        /// Keeps the track of all textures to shut them down
        /// </summary>
        private List<SharpDXTexture> mTextureList = new List<SharpDXTexture>();

        /// <summary>
        /// Keeps the track of all models to free their resources
        /// </summary>
        private List<SharpDXModel> mModelList = new List<SharpDXModel>();

        /// <summary>
        /// Matrix stack
        /// </summary>
        private Stack<Matrix> mMatrices;

        /// <summary>
        /// The current matrix
        /// </summary>
        private Matrix mCurrentMatrix;

        /// <summary>
        /// BackBuffer clearing color
        /// </summary>
        private Vector4 mClearColor;

        /// <summary>
        /// DX Background class
        /// </summary>
        private SharpDXBackground mBackground;

        /// <summary>
        /// DX Rendering texture
        /// </summary>
        private SharpDXRenderTexture mRenderTexture;
      
        /// <summary>
        /// Constructor
        /// </summary>
        public SharpDXAdapter()
        {
            // init controls
            mRenderControl = new RenderControl();
            mHost = new WindowsFormsHost();
            mHost.Child = mRenderControl;

            // init graphics DX subsystem
            mConfiguration = new SystemConfiguration();
            mGraphics = new GraphicsClass();
            mGraphics.Initialize(mConfiguration, mRenderControl.Handle);
            mRenderControl.Paint += mRenderControl_Paint;
            mRenderControl.Resize += mRenderControl_Resize;

            // bind the mouse input
            mRenderControl.MouseDown += mRenderControl_MouseDown;
            mRenderControl.MouseMove += mRenderControl_MouseMove;
            mRenderControl.MouseUp += mRenderControl_MouseUp;
            mRenderControl.MouseWheel += mRenderControl_MouseWheel;

            mMatrices = new Stack<Matrix>();
        }

        /// <summary>
        /// Returns the control
        /// </summary>
        public DependencyObject Control
        {
            get { return mHost; }
        }

        /// <summary>
        /// Rendering request event
        /// </summary>
        public event EventHandler RenderRequestEvent;

        /// <summary>
        /// Mouse handling events
        /// </summary>
        public event EventHandler<EMouseEventArgs> LeftButtonDownEvent;
        public event EventHandler<EMouseEventArgs> LeftButtonUpEvent;
        public event EventHandler<EMouseEventArgs> RightButtonDownEvent;
        public event EventHandler<EMouseEventArgs> RightButtonUpEvent;
        public event EventHandler<EMouseEventArgs> MouseMoveEvent;
        public event EventHandler<EMouseEventArgs> MouseWheelEvent;

        // TO-DO: Is for anything???
        public IRenderingControl RenderingControl
        {
            get { throw new NotImplementedException(); }
        }

        /// <summary>
        /// True flag, when the initialization process of the engine is completed
        /// </summary>
        public bool IsInitializationCompleted
        {
            get { return true;  }
        }


        /// <summary>
        /// Closes the engine
        /// </summary>
        public void Close()
        {
            // unbind events
            mRenderControl.Paint -= mRenderControl_Paint;
            mRenderControl.Resize -= mRenderControl_Resize;
            mRenderControl.MouseDown -= mRenderControl_MouseDown;
            mRenderControl.MouseMove -= mRenderControl_MouseMove;
            mRenderControl.MouseUp -= mRenderControl_MouseUp;
            mRenderControl.MouseWheel -= mRenderControl_MouseWheel;

            // free the allocated models
            for (int i = 0; i < mModelList.Count; i++)
            {
                mModelList[i].Shutdown();
            }
            mModelList.Clear();
            
            // free the textures
            for (int i = 0; i < mTextureList.Count; i++)
            {
                mTextureList[i].Shutdown();
            }
            mTextureList.Clear();
            mCurrentTexture = null;

            // free DX resources
            if (mRenderTexture != null)
            {
                mRenderTexture.Shutdown();
                mRenderTexture = null;
            }
            if (mBackground != null)
            {
                mBackground.Shutdown();
                mBackground = null;
            }
            if (mGraphics != null)
            {
                mGraphics.Shutdown();
                mGraphics = null;                
            }

            // free the rendering control
            if (mRenderControl != null)
            {
                mRenderControl.Dispose();
                mRenderControl = null;
            }
            if (mHost != null)
            {
                mHost.Dispose();
                mHost = null;
            }
        }

        /// <summary>
        /// Returns the size of the rendering control
        /// </summary>
        public System.Windows.Size GetViewportSize()
        {
            return new System.Windows.Size(mRenderControl.Width, mRenderControl.Height);
        }

        /// <summary>
        /// Returns the world matrix
        /// Will there be any global world matrix?
        /// </summary>
        public EMatrix4 GetWorldMatrix()
        {
            return Matrix.Identity.ToEE();
        }

        /// <summary>
        /// Returns the view matrix
        /// </summary>
        public EMatrix4 GetViewMatrix()
        {
            return mGraphics.mCamera.ViewMatrix.ToEE();
        }

        /// <summary>
        /// Returns the projection matrix
        /// </summary>
        public EMatrix4 GetProjectionMatrix()
        {
            return mGraphics.mCamera.ProjectionMatrix.ToEE();            
        }

        /// <summary>
        /// Captures the mouse
        /// </summary>        
        public void SetCapture()
        {
            mRenderControl.Capture = true;
        }

        /// <summary>
        /// Releases the mouse capture
        /// </summary>
        public void ReleaseCapture()
        {
            mRenderControl.Capture = false;
        }

        /// <summary>
        /// Starts rendering 
        /// </summary>
        public void BeginRender()
        {
            // prepare matrix stack
            mCurrentMatrix = Matrix.Identity;
            mMatrices.Clear();

            // clear the target buffer
            mGraphics.BeginRender(mClearColor);

            // render the background
            if (mBackground != null)
            {
                mBackground.Render(mRenderControl.Width, mRenderControl.Height);
            }
        }

        /// <summary>
        /// Ends rendering
        /// </summary>
        public void EndRender()
        {
            mGraphics.EndRender();
        }

        /// <summary>
        /// Begins rendering points
        /// </summary>
        public void BeginPoints()
        {
            // render camera and set values to its components 
            // TO-DO: Redo camera 
            mGraphics.PrimitivesRenderer.SetTransformMatrices(mCurrentMatrix, mGraphics.mCamera.ViewMatrix, mGraphics.mCamera.ProjectionMatrix);
            mGraphics.PrimitivesRenderer.BeginPoints();
        }

        /// <summary>
        /// Ends rendering points
        /// </summary>
        public void EndPoints()
        {
            mGraphics.PrimitivesRenderer.EndPoints();
        }

        /// <summary>
        /// Begins rendering lines
        /// </summary>
        public void BeginLines()
        {
            // TO-DO: Redo camera 
            mGraphics.PrimitivesRenderer.SetTransformMatrices(mCurrentMatrix, mGraphics.mCamera.ViewMatrix, mGraphics.mCamera.ProjectionMatrix);
            mGraphics.PrimitivesRenderer.BeginLines();
        }

        /// <summary>
        /// Ends rendering lines
        /// </summary>
        public void EndLines()
        {
            mGraphics.PrimitivesRenderer.EndLines();
        }

        /// <summary>
        /// Setups the camera settings
        /// </summary>
        public void SetCamera(EVector3 position, EVector3 lookAt, EVector3 up)
        {
            mGraphics.SetCamera(position.ToDX().ToLHS(), lookAt.ToDX().ToLHS(), up.ToDX().ToLHS());
        }

        /// <summary>
        /// Sets the FOV of camera
        /// </summary>
        public void SetFOV(float fov)
        {
            mGraphics.SetFOV(fov);
            mGraphics.mCamera.UpdatePerspective(mRenderControl.Width, mRenderControl.Height);
        }
        /// <summary>
        /// Sets the near and far limits of the camera
        /// </summary>
        public void SetNearFarLimits(float near, float far)
        {
            mGraphics.SetNearFarLimits(near, far);
            mGraphics.mCamera.UpdatePerspective(mRenderControl.Width, mRenderControl.Height);
        }


        /// <summary>
        /// Renders a point
        /// </summary>
        public void RenderPoint(EVector3 p0, EVector4 color)
        {
            mGraphics.PrimitivesRenderer.RenderPoint(p0.ToDX().ToLHS(), color.ToDX());
        }

        /// <summary>
        /// Renders a line
        /// </summary>
        public void RenderLine(EVector3 p1, EVector3 p2, EVector4 color)
        {
            mGraphics.PrimitivesRenderer.RenderLine(p1.ToDX().ToLHS(), p2.ToDX().ToLHS(), color.ToDX());
        }

        /// <summary>
        /// Renders the model
        /// </summary>
        public void RenderModel(EModel model)
        {
            if (mCurrentTexture == null)
                throw new ApplicationException("No texture is selected");

            (model as SharpDXModel).Render(mCurrentMatrix, mCurrentTexture.GetResource());
        }

        /// <summary>
        /// Method for rendering the normal vector of the given model
        /// </summary>
        public void RenderNormals(EModel model, float magnitude, EVector4 color)
        {
            (model as SharpDXModel).RenderNormals(mCurrentMatrix, color.ToDX(), magnitude);
        }

        /// <summary>
        /// Creates DX model
        /// </summary>
        public EModel CreateModel()
        {
            SharpDXModel model = new SharpDXModel(mGraphics.DirectXDevice, mRenderControl.Handle, mGraphics.mCamera, mGraphics.PrimitivesRenderer, mGraphics.LightingManager,
                                    mGraphics.TextureShader, mGraphics.LightingShader);
            mModelList.Add(model);
            return model;
        }

        /// <summary>
        /// Factory method for a SharpDX texture
        /// </summary>
        public ETexture CreateTexture()
        {
            SharpDXTexture texture = new SharpDXTexture(mGraphics.DirectXDevice);
            mTextureList.Add(texture);
            return texture;
        }

        /// <summary>
        /// Pushes the current matrix
        /// </summary>
        public void PushMatrix()
        {
            mMatrices.Push(mCurrentMatrix);
        }

        /// <summary>
        /// Multiplies the current matrix
        /// </summary>
        public void MultMatrix(ref EMatrix4 matrix)
        {
            mCurrentMatrix = Matrix.Multiply(matrix.ToDX(), mCurrentMatrix);
        }

        /// <summary>
        /// Makes the current matrix identity
        /// </summary>
        public void LoadIdentity()
        {
            mCurrentMatrix = Matrix.Identity;
        }

        /// <summary>
        /// Pops a matrix from the stack
        /// </summary>
        public void PopMatrix()
        {
            mCurrentMatrix = mMatrices.Pop();                        
        }

        /// <summary>
        /// Sets the alpha blending transparency
        /// </summary>
        public void SetAlphaBlend(float alpha)
        {
            mGraphics.mCamera.AlphaOpacity = alpha;

            if (Math.Abs(alpha - 0.9999) < 0.0001)
            {
                mGraphics.DisableBlendState();
            }
            else
            {
                mGraphics.EnableBlendState();
            }
        }

        /// <summary>
        /// Sets the current rendering material parameters for the mesh
        /// </summary>
        public void SetMaterial(EMaterial material)
        {
            mGraphics.LightingManager.SetMaterial(material);
        }

        /// <summary>
        /// Sets the current texture
        /// </summary>
        public void SetTexture(ETexture texture)
        {
            mCurrentTexture = texture as SharpDXTexture;
        }

        /// <summary>
        /// Sets the back buffer clearing color
        /// </summary>
        public void SetClearColor(EVector4 color)
        {
            mClearColor = color.ToDX();
        }

        /// <summary>
        /// Sets the rendered view's background image
        /// </summary>
        public void SetBackground(IBackground background)
        {
            mBackground = new SharpDXBackground(background, mGraphics.DirectXDevice);
        }

        /// <summary>
        /// Enables or disables lighting
        /// </summary>
        public void EnableLighting(bool enabled)
        {
            mGraphics.LightingManager.LightingEnabled = enabled;
        }

        /// <summary>
        /// Creates a new light
        /// </summary>
        public ELight CreateLight()
        {
            return mGraphics.LightingManager.CreateLight();
        }

        /// <summary>
        /// Renders a light
        /// </summary>
        public void RenderLight(ELight light)
        {
            mGraphics.LightingManager.RenderLight(light, mCurrentMatrix);
        }

        /// <summary>
        /// Sets the global ambient light
        /// </summary>
        public void SetGlobalAmbient(EVector3 color)
        {
            mGraphics.LightingManager.SetGlobalAmbientLight(color.ToDX());
        }

        /// <summary>
        /// Initializes the rendering texture
        /// </summary>
        public void InitializeRenderingTexture(int width, int height)
        {
            if (mRenderTexture != null)
            {
                mRenderTexture.Shutdown();
                mRenderTexture = null;
            }

            mRenderTexture = new SharpDXRenderTexture(mGraphics.DirectX11, width, height);
        }

        /// <summary>
        /// Begins rendering to texture
        /// </summary>
        public void BeginRenderToTexture()
        {
            mRenderTexture.BindTarget();
        }

        /// <summary>
        /// Draws a texture to the rendering texture
        /// </summary>
        public void DrawTexture(ETexture texture, int x, int y, int width, int height)
        {
            mRenderTexture.DrawTexture(x, y, width, height, texture as SharpDXTexture);
        }

        /// <summary>
        /// Draws a rect to the rendering texture
        /// </summary>
        public void DrawRect(int x, int y, int width, int height, EVector4 color)
        {
            mRenderTexture.DrawRect(x, y, width, height, color.ToDX());
        }

        /// <summary>
        /// Ends drawing to the rendering texture
        /// </summary>
        public ETexture EndRenderToTexture()
        {
            mRenderTexture.UnbindTarget(mRenderControl.Width, mRenderControl.Height);
            return mRenderTexture;
        }

        /// <summary>
        /// Not used here
        /// </summary>
        public void InvokeRendering()
        {
            throw new NotImplementedException();
        }

        #region Handlers

        /// <summary>
        /// Mouse down event
        /// </summary>
        private void mRenderControl_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            if (e.Button == System.Windows.Forms.MouseButtons.Left)
            {
                if (LeftButtonDownEvent != null)
                {
                    EMouseEventArgs args = new EMouseEventArgs();
                    args.Position = new System.Windows.Point(e.Location.X, e.Location.Y);
                    LeftButtonDownEvent(this, args);
                }
            }
            else if (e.Button == System.Windows.Forms.MouseButtons.Right)
            {
                if (RightButtonDownEvent != null)
                {
                    EMouseEventArgs args = new EMouseEventArgs();
                    args.Position = new System.Windows.Point(e.Location.X, e.Location.Y);
                    RightButtonDownEvent(this, args);
                }
            }
        }

        /// <summary>
        /// Mouse move event
        /// </summary>
        private void mRenderControl_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            if (MouseMoveEvent != null)
            {
                EMouseEventArgs args = new EMouseEventArgs();
                args.Position = new System.Windows.Point(e.Location.X, e.Location.Y);
                MouseMoveEvent(this, args);
            }
        }

        /// <summary>
        /// Mouse up event
        /// </summary>
        private void mRenderControl_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            if (e.Button == System.Windows.Forms.MouseButtons.Left)
            {
                if (LeftButtonUpEvent != null)
                {
                    EMouseEventArgs args = new EMouseEventArgs();
                    args.Position = new System.Windows.Point(e.Location.X, e.Location.Y);
                    LeftButtonUpEvent(this, args);
                }
            }
            else if (e.Button == System.Windows.Forms.MouseButtons.Right)
            {
                if (RightButtonUpEvent != null)
                {
                    EMouseEventArgs args = new EMouseEventArgs();
                    args.Position = new System.Windows.Point(e.Location.X, e.Location.Y);
                    RightButtonUpEvent(this, args);
                }
            }
        }

        /// <summary>
        /// Event when mouse wheel is rotated
        /// </summary>
        private void mRenderControl_MouseWheel(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            if (MouseWheelEvent != null)
            {
                EMouseEventArgs args = new EMouseEventArgs();
                args.Position = new System.Windows.Point(e.Location.X, e.Location.Y);
                args.WheelDelta = e.Delta;

                MouseWheelEvent(this, args);
            }
        }

        /// <summary>
        /// Paint request from the OS
        /// </summary>
        private void mRenderControl_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
        {
            if (RenderRequestEvent != null)
            {
                RenderRequestEvent(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Resize event of the render control
        /// </summary>
        private void mRenderControl_Resize(object sender, EventArgs e)
        {
            mGraphics.UpdatePerspective(mRenderControl.Width, mRenderControl.Height);
            mGraphics.Resize(mRenderControl.Width, mRenderControl.Height == 0 ? 1 : mRenderControl.Height);
        }


        #endregion
    }
}
